package ru.ifmo.ct.network.brafs.node;

import ru.ifmo.ct.network.brafs.IllegalCommandFormatException;
import ru.ifmo.ct.network.brafs.Utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;

/**
 * @author Alex Vikharev vikharev@yandex-team.ru
 *         created 20.04.12
 */
public class Command {

    private final CommandType type;
    private final byte[] bytes;
    private final ByteBuffer buffer;

    private Command(final CommandType type, final byte[] bytes) {
        this.type = type;
        this.bytes = bytes;
        buffer = ByteBuffer.wrap(bytes);
    }

    public CommandType getType() {
        return type;
    }

    public byte[] getIp() {
        final byte[] ip = new byte[4];
        buffer.position(1);          //todo wtf!? cursor changed!!11
        buffer.get(ip, 0, 4);
        return ip;
    }

    public int getPort() {
        return buffer.getInt(5); //TODO fixme
    }

    public long getSegmentId() {
        return buffer.getLong(9);
    }

    public String getFileName() {
        final int len = buffer.getInt(17);
        if (len == 0) {
            return "";
        }
        final byte[] rawName = new byte[len];
        buffer.position(21);
        buffer.get(rawName, 0, len);
        return new String(rawName, Charset.forName("utf-8"));
    }

    public byte[] getBytes() {
        return bytes.clone();
    }

    public void write(final OutputStream stream) throws IOException {
        stream.write(bytes);
    }

    public Command(final CommandType type, final long idSeg, final String name) {
        this(type, new byte[4], 0, idSeg, name);
    }

    public Command(final CommandType type,
                   final byte[] ip,
                   final int port,
                   final long idSeg,
                   final String name) {
        if (ip.length != 4) {
            throw new IllegalArgumentException("IP array mush have four bytes");
        }

        final byte[] bytes = name.getBytes(Charset.forName("utf-8"));
        final ByteBuffer buffer = ByteBuffer.allocate(21 + bytes.length);
        buffer.put(type.code);
        buffer.put(ip);
        buffer.putInt(port);
        buffer.putLong(idSeg);
        buffer.putInt(bytes.length);
        buffer.put(bytes);

        this.type = type;
        this.bytes = buffer.array();
        this.buffer = buffer;
    }

    public static Command read(final InputStream inputStream) throws IOException, IllegalCommandFormatException {
        final ByteBuffer buffer = Utils.readCommand(inputStream);
        if (buffer.position() < 21) {
            throw new IllegalCommandFormatException("Error in command format: command length must be at least 21 bytes");
        }
        if (CommandType.byCode(buffer.get(0)) == CommandType.UNKNOWN) {
            throw new IllegalCommandFormatException("Error in command format: unknown command type");
        }
        if (buffer.position() != 21 + buffer.getInt(17)) {
            throw new IllegalCommandFormatException("Error in command format: unexpected command length. Expected: " +
                    (21 + buffer.getInt(17)) + " Got: " + buffer.position());
        }
        return new Command(CommandType.byCode(buffer.get(0)), buffer.array());
    }

    @Override
    public String toString() {
        return "Command{" +
                "type=" + type + ", ip=" + toString(getIp()) + ", port=" + getPort() + ", segment_id=" + getSegmentId()
                + ", name=" + getFileName() +
                '}';
    }

    private static String toString(byte[] array) {
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (int i = 0; i < array.length; i++) {
            byte b = array[i];
            sb.append(b & 0xFF);
            if (i == array.length - 1) {
                return sb.append("]").toString();
            }
            sb.append(", ");
        }
        return sb.toString();
    }

}
